#include "at24c02.h"

// 宏定义引脚位带名
#define SCL PBout(6)    // 时钟的读取引脚位带
#define SDA_IN PBout(7) // 数据输入引脚位带
#define SDA_OUT PBin(7) // 数据输出引脚位带

//---------------------------底层接口----------------------------------//

/*
    函数功能：初始化AT24C02的控制引脚
    输入参数：无
    返回值：无
*/

void AT24C02_Init(void)
{
    // 初始化时钟引脚为输出模式
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    // 初始化数据引脚为输出模式
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; // 开漏输出，当拉高后可以从此引脚读取数据
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    SCL = 1;     // 拉高时钟线
    SDA_OUT = 1; // 拉高数据线
}

#define AT24C02_ADDR 0xa0 // AT24C02的地址

/*
    发送启动信号
    产生一个下降沿，并拉低时钟
    ps在发送启动信号前时钟线为低电平，才能在启动信号的拉高时钟线的操作发送出去
*/
static void AT24C02_Start(void)
{
    SDA_OUT = 1; // 拉高数据线
    SCL = 1;     // 拉高时钟线
    DelayUS(1);
    SDA_OUT = 0; // 拉低数据线
    DelayUS(1);
    SCL = 0; // 拉低时钟线
    DelayUS(1);
}
/*
    发送结束信号
    拉高时钟线，产生一个上升沿
*/
static void AT24C02_Stop(void)
{
    SDA_OUT = 0; // 拉低数据线
    SCL = 1;     // 拉高时钟线
    DelayUS(1);
    SDA_OUT = 1; // 拉高数据线

}

/*
    应答检测
    等待从机的响应信号，如果响应则返回1，否则返回0
*/
static u8 AT24C02_WaitAck(void)
{

}

/*
    发送应答信号
*/
static void AT24C02_SendACK(u8 ack)
{
    SCL = 0;    // 产生第九个时钟上升沿，为了获取从机的应答
    SDA = ack;    // 主机释放总线
    DelayUS(1);
    SCL = 1;
    DelayUS(1);
}

// IIC写一个字节
static void IIC_Write(u8 val)
{
    for (u8 i = 0;i < 8; i++) {

        SDA = (val & 0x80) == 0x80 ? 1 : 0;
        SCL = 1;
        DelayUS(1); // 从机在SCL的高电平期间读取总线电平
        val <<= 1;
        
        SCL = 0;
        DelayUS(1);
    }
}

// TTC读一个字节
static u8 IIC_Read(void)
{
    u8 val = 0;
    SDA = 1; // ÊÍ·Å×ÜÏß
    for (u8 i = 0;i < 8; i++) {
        SCL = 1;    // 拉高时钟，主机读取数据
        DelayUS(1); // 从机在SCL的高点平期间读取总线电平
        val <<= 1;
        val |= SDA_IN;
        
        SCL = 0; // 拉低时钟，从机写出数据
        DelayUS(1); // 当前时间，EEPROM写出一位数据
    }
    
    return val;
}
//------------------------------------------------------------------//
//---------------------------高级接口---------------------------------//
/*
    函数功能：向AT24C02中指定地址，写入一个字节
*/
bool AT24C02_WriteByte(u8 mem_addr, u8 val)
{
    // 1. 发送启动信号
    AT24C02_Start();
    
    // 2. 发送设备地址
    IIC_Write(AT24C02_ADDR);
    
    // 3. 检查应答
    if (! IIC_CheckACK())
        goto fail;
    
    // 4. 发存储地址
    IIC_Write(mem_addr);
    
    // 5. 检查应答
    if (! IIC_CheckACK())
        goto fail;
    
    // 6. 写入数据
    IIC_Write(val);
    
    // 7. 检查应答
    if (! IIC_CheckACK())
        goto fail;
    
    // 8. 发送结束信号
    AT24C02_Stop();
    printf("AT24C02_WriteByte---\r\n");
    return true;
fail:    
    AT24C02_Stop();
    printf("write fail\r\n");
    return false;
}

/*
函数功能：向AT24C02中指定地址，读取一个字节
*/
bool AT24C02_ReadByte(u8 mem_addr, u8 *val)
{
    // 1. 发送启动信号
    AT24C02_Start();
    
    // 2. 发送设备地址(写)
    IIC_Write(AT24C02_ADDR);
    
    // 3. 检查应答
    if (! IIC_CheckACK())
        goto fail;
    
    // 4. 发存储地址(读)
    IIC_Write(mem_addr);
    
    // 5. 检查应答
    if (! IIC_CheckACK())
        goto fail;
    
    // 6. 重新发送开始信号
    AT24C02_Start();
    
    // 7. 发送设备地址(读)
    IIC_Write(AT24C02_ADDR | 1);
    
    // 8. 检查应答
    if (! IIC_CheckACK())
        goto fail;
    
    // 9. 读取数据
    *val = IIC_Read();
    
    // 10. 发送非应答
    IIC_SendACK(NACK);
    
    // 11. 发送结束信号
    AT24C02_Stop();
    printf("AT24C02_ReadByte---\r\n");
    return true;
fail:    
    AT24C02_Stop();
    printf("read fail\r\n");
    return false;
}
